
// gcc -O2 -maltivec -o writemem writemem.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <wchar.h>
#include <sys/time.h>

#ifdef __amigaos4__
#include <proto/exec.h>
#else
#define TRUE 1
#define FALSE 0
#endif

#ifdef USE_ALTIVEC
#include <altivec.h>
#endif

#define NB_SECONDS 12

#define BUFFER_SIZE_BYTES (1024 * 768 * 4)
#define BUFFER_SIZE_WORDS (BUFFER_SIZE_BYTES / sizeof(int))
#define BUFFER_SIZE_FLOATS (BUFFER_SIZE_BYTES / sizeof(float))
#define BUFFER_SIZE_DOUBLES (BUFFER_SIZE_BYTES / sizeof(double))
#define BUFFER_SIZE_MEGABYTES (BUFFER_SIZE_BYTES / (1024 * 1024))

#ifdef CPU_PPC440
void memset_stswi(int nloops, int *buffer, int len, int value);
#endif

static void buffer_fill(int *buffer, int len, int value)
{
   int i;

   for (i=0 ; i<len ; i++) {
      buffer[i] = value;
   }
}

// Check that a buffer is filled with a given value.
// Return TRUE if success
static int buffer_check_fill_value(int *buffer, int len, int value)
{
   int i;

   for (i=0 ; i<len ; i++) {
      if (buffer[i] != value) {
         printf("buffer_check_fill_value failed at offset %d (expected 0x%08x, found 0x%08x)\n", i, value, buffer[i]);
         return FALSE;
      }
   }

   return TRUE;
}

int main(int argc, char **argv)
{
   int *buf, *addr_buf;
   int i, n, nloops;
   struct timeval tv_start, tv_stop, tv_diff;
   float ftime;
   volatile char *cptr;
   volatile int *iptr;
   volatile float *fptr;
   volatile double *dptr;
#ifdef USE_ALTIVEC
   vector unsigned int *vptr;
   vector unsigned int value = vec_splat_u32(0);
   vector unsigned int value2 = vec_splat_u32(0);
#endif

   printf("sizeof(float) = %d\n", sizeof(float));
   printf("sizeof(double) = %d\n", sizeof(double));
   printf("sizeof(wchar_t) = %d\n", sizeof(wchar_t));

   addr_buf = malloc(BUFFER_SIZE_BYTES + 128);
   if (!addr_buf) {
      printf("Impossible to allocate buffer\n");
      return 1;
   }
   printf("Allocated %.1f KB buffer at %p\n", (float)BUFFER_SIZE_BYTES / 1024, addr_buf);
   buf = addr_buf;
   if ((int)addr_buf & 0x1f) {
      buf = (int *)(((int)addr_buf + 32) & ~0x1f);
      printf("Buffer not aligned, realign it at %p\n", buf);
   }

   buffer_fill(buf, BUFFER_SIZE_WORDS, 0xdeadcafe);
   if (buffer_check_fill_value(buf, BUFFER_SIZE_WORDS, 0xdeadcafe) == FALSE) {
      printf("Check buffer is filled of 0 : failure\n");
   }

   /*
    * Really allocate the pages
    * Linux only allocates pages the first time they are accessed, so run a first loop
    * for that and to compute the number of loops in the following tests, based on the
    * slowest method.
    */
   gettimeofday(&tv_start, NULL);
   nloops = 0;
   buffer_fill(buf, BUFFER_SIZE_WORDS, 0xdeadcafe);

   do {
      cptr = (char *)buf;
      for (i=0 ; i< BUFFER_SIZE_BYTES ; i++) {
         *cptr++ = 0;
      }
      gettimeofday(&tv_stop, NULL);
      timersub(&tv_stop, &tv_start, &tv_diff);
      nloops++;
   } while (tv_diff.tv_sec < NB_SECONDS);
   printf("Calibration done: %d.%03d s, each test will run %d loops\n", (int)tv_diff.tv_sec, (int)(tv_diff.tv_usec / 1000), nloops);
   if (buffer_check_fill_value(buf, BUFFER_SIZE_WORDS, 0) == FALSE) {
      printf("Check buffer is filled of 0 : failure\n");
   }


   /*
    * Clear by memset 0
    */
   buffer_fill(buf, BUFFER_SIZE_WORDS, 0xdeadcafe);
   gettimeofday(&tv_start, NULL);
   for (n=0 ; n<nloops ; n++) {
      memset(buf, 0, BUFFER_SIZE_BYTES);
   }
   gettimeofday(&tv_stop, NULL);
   timersub(&tv_stop, &tv_start, &tv_diff);
   ftime = tv_diff.tv_sec + ((float)tv_diff.tv_usec / 1000000);
   printf("Clear by memset 0 : time = %.03f s, bandwidth = %d MB/s\n", ftime, (int)(BUFFER_SIZE_MEGABYTES * nloops / ftime));
   if (buffer_check_fill_value(buf, BUFFER_SIZE_WORDS, 0) == 0) {
      printf("Check buffer is filled of 0 : failure\n");
   }

   /*
    * Clear by memset 1
    */
   buffer_fill(buf, BUFFER_SIZE_WORDS, 0xdeadcafe);
   gettimeofday(&tv_start, NULL);
   for (n=0 ; n<nloops ; n++) {
      memset(buf, 1, BUFFER_SIZE_BYTES);
   }
   gettimeofday(&tv_stop, NULL);
   timersub(&tv_stop, &tv_start, &tv_diff);
   ftime = tv_diff.tv_sec + ((float)tv_diff.tv_usec / 1000000);
   printf("Clear by memset 1 : time = %.03f s, bandwidth = %d MB/s\n", ftime, (int)(BUFFER_SIZE_MEGABYTES * nloops / ftime));
   if (buffer_check_fill_value(buf, BUFFER_SIZE_WORDS, 0x01010101) == 0) {
      printf("Check buffer is filled of 0 : failure\n");
   }

   /*
    * Clear by wmemset 0
    */
   buffer_fill(buf, BUFFER_SIZE_WORDS, 0xdeadcafe);
   gettimeofday(&tv_start, NULL);
   for (n=0 ; n<nloops ; n++) {
      wmemset((wchar_t *)buf, 0, BUFFER_SIZE_WORDS);
   }
   gettimeofday(&tv_stop, NULL);
   timersub(&tv_stop, &tv_start, &tv_diff);
   ftime = tv_diff.tv_sec + ((float)tv_diff.tv_usec / 1000000);
   printf("Clear by wmemset 0 : time = %.03f s, bandwidth = %d MB/s\n", ftime, (int)(BUFFER_SIZE_MEGABYTES * nloops / ftime));
   if (buffer_check_fill_value(buf, BUFFER_SIZE_WORDS, 0) == 0) {
      printf("Check buffer is filled of 0 : failure\n");
   }

   /*
    * Clear by wmemset 1
    */
   buffer_fill(buf, BUFFER_SIZE_WORDS, 0xdeadcafe);
   gettimeofday(&tv_start, NULL);
   for (n=0 ; n<nloops ; n++) {
      wmemset((wchar_t *)buf, 1, BUFFER_SIZE_WORDS);
   }
   gettimeofday(&tv_stop, NULL);
   timersub(&tv_stop, &tv_start, &tv_diff);
   ftime = tv_diff.tv_sec + ((float)tv_diff.tv_usec / 1000000);
   printf("Clear by wmemset 1 : time = %.03f s, bandwidth = %d MB/s\n", ftime, (int)(BUFFER_SIZE_MEGABYTES * nloops / ftime));
   if (buffer_check_fill_value(buf, BUFFER_SIZE_WORDS, 1) == 0) {
      printf("Check buffer is filled of 0 : failure\n");
   }

   /*
    * Clear by char
    */
   buffer_fill(buf, BUFFER_SIZE_WORDS, 0xdeadcafe);
   gettimeofday(&tv_start, NULL);
   for (n=0 ; n<nloops ; n++) {
      cptr = (char *)buf;
      for (i=0 ; i< BUFFER_SIZE_BYTES ; i++) {
         *cptr++ = 0;
      }
   }
   gettimeofday(&tv_stop, NULL);
   timersub(&tv_stop, &tv_start, &tv_diff);
   ftime = tv_diff.tv_sec + ((float)tv_diff.tv_usec / 1000000);
   printf("Clear by char : time = %.03f s, bandwidth = %d MB/s\n", ftime, (int)(BUFFER_SIZE_MEGABYTES * nloops / ftime));
   if (buffer_check_fill_value(buf, BUFFER_SIZE_WORDS, 0) == 0) {
      printf("Check buffer is filled of 0 : failure\n");
   }

   /*
    * Clear by int
    */
   buffer_fill(buf, BUFFER_SIZE_WORDS, 0xdeadcafe);
   gettimeofday(&tv_start, NULL);
   for (n=0 ; n<nloops ; n++) {
      iptr = (int *)buf;
      for (i=0 ; i< BUFFER_SIZE_WORDS ; i++) {
         *iptr++ = 0;
      }
   }
   gettimeofday(&tv_stop, NULL);
   timersub(&tv_stop, &tv_start, &tv_diff);
   ftime = tv_diff.tv_sec + ((float)tv_diff.tv_usec / 1000000);
   printf("Clear by int : time = %.03f s, bandwidth = %d MB/s\n", ftime, (int)(BUFFER_SIZE_MEGABYTES * nloops / ftime));
   if (buffer_check_fill_value(buf, BUFFER_SIZE_WORDS, 0) == 0) {
      printf("Check buffer is filled of 0 : failure\n");
   }

   /*
    * Clear by float
    */
   buffer_fill(buf, BUFFER_SIZE_WORDS, 0xdeadcafe);
   gettimeofday(&tv_start, NULL);
   for (n=0 ; n<nloops ; n++) {
      fptr = (float *)buf;
      for (i=0 ; i< BUFFER_SIZE_FLOATS ; i++) {
         *fptr++ = 0;
      }
   }
   gettimeofday(&tv_stop, NULL);
   timersub(&tv_stop, &tv_start, &tv_diff);
   ftime = tv_diff.tv_sec + ((float)tv_diff.tv_usec / 1000000);
   printf("Clear by float : time = %.03f s, bandwidth = %d MB/s\n", ftime, (int)(BUFFER_SIZE_MEGABYTES * nloops / ftime));
   if (buffer_check_fill_value(buf, BUFFER_SIZE_WORDS, 0) == 0) {
      printf("Check buffer is filled of 0 : failure\n");
   }

   /*
    * Clear by double
    */
   buffer_fill(buf, BUFFER_SIZE_WORDS, 0xdeadcafe);
   gettimeofday(&tv_start, NULL);
   for (n=0 ; n<nloops ; n++) {
      dptr = (double *)buf;
      for (i=0 ; i< BUFFER_SIZE_DOUBLES ; i++) {
         *dptr++ = 0;
      }
   }
   gettimeofday(&tv_stop, NULL);
   timersub(&tv_stop, &tv_start, &tv_diff);
   ftime = tv_diff.tv_sec + ((float)tv_diff.tv_usec / 1000000);
   printf("Clear by double : time = %.03f s, bandwidth = %d MB/s\n", ftime, (int)(BUFFER_SIZE_MEGABYTES * nloops / ftime));
   if (buffer_check_fill_value(buf, BUFFER_SIZE_WORDS, 0) == 0) {
      printf("Check buffer is filled of 0 : failure\n");
   }

   /*
    * Clear by dcbz
    */
#ifdef __powerpc__
   buffer_fill(buf, BUFFER_SIZE_WORDS, 0xdeadcafe);
   gettimeofday(&tv_start, NULL);
   for (n=0 ; n<nloops ; n++) {
      cptr = (char *)buf;
      for (i=0 ; i< BUFFER_SIZE_BYTES ; i+=32) {
         asm volatile("dcbz %0, %1" : : "b" (cptr), "r" (i) : "memory");
      }
   }
   gettimeofday(&tv_stop, NULL);
   timersub(&tv_stop, &tv_start, &tv_diff);
   ftime = tv_diff.tv_sec + ((float)tv_diff.tv_usec / 1000000);
   printf("Clear by dcbz : time = %.03f s, bandwidth = %d MB/s\n", ftime, (int)(BUFFER_SIZE_MEGABYTES * nloops / ftime));
   if (buffer_check_fill_value(buf, BUFFER_SIZE_WORDS, 0) == 0) {
      printf("Check buffer is filled of 0 : failure\n");
   }
#endif

   /*
    * Clear by int with loop unrolled
    */
   buffer_fill(buf, BUFFER_SIZE_WORDS, 0xdeadcafe);
   gettimeofday(&tv_start, NULL);
   for (n=0 ; n<nloops ; n++) {
      iptr = (int *)buf;
      for (i=0 ; i< BUFFER_SIZE_WORDS / 8; i++) {
         iptr[0] = 0;
         iptr[1] = 0;
         iptr[2] = 0;
         iptr[3] = 0;
         iptr[4] = 0;
         iptr[5] = 0;
         iptr[6] = 0;
         iptr[7] = 0;
         iptr += 8;
      }
   }
   gettimeofday(&tv_stop, NULL);
   timersub(&tv_stop, &tv_start, &tv_diff);
   ftime = tv_diff.tv_sec + ((float)tv_diff.tv_usec / 1000000);
   printf("Clear by int with loop unrolled : time = %.03f s, bandwidth = %d MB/s\n", ftime, (int)(BUFFER_SIZE_MEGABYTES * nloops / ftime));
   if (buffer_check_fill_value(buf, BUFFER_SIZE_WORDS, 0) == 0) {
      printf("Check buffer is filled of 0 : failure\n");
   }


   /*
    * Clear by int with loop unrolled and prefetch
    */
#ifdef __powerpc__
   buffer_fill(buf, BUFFER_SIZE_WORDS, 0xdeadcafe);
   gettimeofday(&tv_start, NULL);
   for (n=0 ; n<nloops ; n++) {
      iptr = (int *)buf;
      for (i=0 ; i< BUFFER_SIZE_WORDS / 8; i++) {
         asm volatile ("dcbtst 0, %0" : : "r" (iptr + 8) : "memory");
         iptr[0] = 0;
         iptr[1] = 0;
         iptr[2] = 0;
         iptr[3] = 0;
         iptr[4] = 0;
         iptr[5] = 0;
         iptr[6] = 0;
         iptr[7] = 0;
         iptr += 8;
      }
   }
   gettimeofday(&tv_stop, NULL);
   timersub(&tv_stop, &tv_start, &tv_diff);
   ftime = tv_diff.tv_sec + ((float)tv_diff.tv_usec / 1000000);
   printf("Clear by int with loop unrolled and prefetch : time = %.03f s, bandwidth = %d MB/s\n", ftime, (int)(BUFFER_SIZE_MEGABYTES * nloops / ftime));
   if (buffer_check_fill_value(buf, BUFFER_SIZE_WORDS, 0) == 0) {
      printf("Check buffer is filled of 0 : failure\n");
   }
#endif

#ifdef USE_ALTIVEC
   /*
    * Clear by AltiVec
    */
   buffer_fill(buf, BUFFER_SIZE_WORDS, 0xdeadcafe);
   gettimeofday(&tv_start, NULL);
   for (n=0 ; n<nloops ; n++) {
      vptr = (vector unsigned int *)buf;
      for (i=0 ; i<BUFFER_SIZE_BYTES/16 ; i++) {
         vec_st(value, 0, vptr);
         vptr++;
      }
   }
   gettimeofday(&tv_stop, NULL);
   timersub(&tv_stop, &tv_start, &tv_diff);
   ftime = tv_diff.tv_sec + ((float)tv_diff.tv_usec / 1000000);
   printf("Clear by altivec : time = %.03f s, bandwidth = %d MB/s\n", ftime, (int)(BUFFER_SIZE_MEGABYTES * nloops / ftime));
   if (buffer_check_fill_value(buf, BUFFER_SIZE_WORDS, 0) == 0) {
      printf("Check buffer is filled of 0 : failure\n");
   }

   /*
    * Clear by AltiVec 2 (use two variables to unroll)
    */
   buffer_fill(buf, BUFFER_SIZE_WORDS, 0xdeadcafe);
   gettimeofday(&tv_start, NULL);
   for (n=0 ; n<nloops ; n++) {
      vptr = (vector unsigned int *)buf;
      for (i=0 ; i<BUFFER_SIZE_BYTES/32 ; i++) {
         vec_st(value, 0, vptr);
         vec_st(value2, 16, vptr);
         vptr += 2;
      }
   }
   gettimeofday(&tv_stop, NULL);
   timersub(&tv_stop, &tv_start, &tv_diff);
   ftime = tv_diff.tv_sec + ((float)tv_diff.tv_usec / 1000000);
   printf("Clear by altivec 2 : time = %.03f s, bandwidth = %d MB/s\n", ftime, (int)(BUFFER_SIZE_MEGABYTES * nloops / ftime));
   if (buffer_check_fill_value(buf, BUFFER_SIZE_WORDS, 0) == 0) {
      printf("Check buffer is filled of 0 : failure\n");
   }
#endif

   /*
    * Clear by stswi
    * This instruction is specific to the PowerPC 440
    */
#ifdef CPU_PPC440
   buffer_fill(buf, BUFFER_SIZE_WORDS, 0xdeadcafe);
   gettimeofday(&tv_start, NULL);

   // r3 = number of the benchmark loops
   // r4 = buffer pointer
   // r5 = number of words in the buffer
   // r6 = value to set (int)
   memset_stswi(nloops, (int *)buf, BUFFER_SIZE_WORDS, 0);

   gettimeofday(&tv_stop, NULL);
   timersub(&tv_stop, &tv_start, &tv_diff);
   ftime = tv_diff.tv_sec + ((float)tv_diff.tv_usec / 1000000);
   printf("Clear by stswi : time = %.03f s, bandwidth = %d MB/s\n", ftime, (int)(BUFFER_SIZE_MEGABYTES * nloops / ftime));
   if (buffer_check_fill_value(buf, BUFFER_SIZE_WORDS, 0) == 0) {
      printf("Check buffer is filled of 0 : failure\n");
   }

   // Try another variant

#endif

   /*
    * Free resources and exit
    */
   free(addr_buf);

   return 0;
}

